package cn.xw.utils.security.rsa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Enumeration;
import java.util.List;

/**
 * 数字证书和密钥库的解析（密钥库解析只能解析证书信息，无法解析密钥库内私钥信息）
 * 注：若解析密钥库内的私钥信息，只会返回一条密钥的Base64字符串信息（不推荐使用，推荐每次解密信息时从密钥库获取私钥）
 * 注：密钥库解析只测试（PKCS12、JKS），其它密钥库格式未测试过
 *
 * @author Anhui AntLaddie（博客园蚂蚁小哥）
 * @version 1.0
 **/
public class BaseParsing {

    private static final Logger log = LoggerFactory.getLogger(BaseParsing.class);

    // 证书类型
    private static final String CERTIFICATE_TYPE = "X.509";

    /***
     * 根据传入的证书流对象，判断证书是否过期
     * @param certificateStream 证书流信息
     * @return true代表证书过期活未生效，false代表证书未过期
     */
    public static boolean certificateWhetherOverdue(InputStream certificateStream) {
        // 证书解析
        CertificateMessage certificateMessage = certificateParsing(certificateStream);
        return certificateVerificationTime(certificateMessage);
    }

    /***
     * 根据传入的证书流对象，判断证书是否过期
     * @param certificateBase64 证书的Base64字符串信息
     * @return true代表证书过期活未生效，false代表证书未过期
     */
    public static boolean certificateWhetherOverdue(String certificateBase64) {
        // 证书解析
        CertificateMessage certificateMessage = certificateParsing(certificateBase64);
        return certificateVerificationTime(certificateMessage);
    }

    /***
     * 根据传入的密钥库流信息解析密钥库内的指定别名的私钥信息（切忌不要到处暴露或日志打印）
     * @param keystoreStream 密钥库流对象信息
     * @param keyStoreType 密钥库类型，如：PKCS12、JKS
     * @param keystorePwd 密钥库密码（若密钥库未设置密码则直接传入null）
     * @param privateKeyPwd 密钥库内私钥密码（若密钥库未设置密码则直接传入null）
     * @param privateKeyAlias 密钥库内私钥别名
     * @return 返回字符串类型的私钥信息
     */
    public static String analysisKeystorePrivateKey(InputStream keystoreStream,
                                                    String keyStoreType,
                                                    String keystorePwd,
                                                    String privateKeyPwd,
                                                    String privateKeyAlias) {
        // 初始化数据
        String resultPrivateKey = "";
        // 构建密钥库对象，并设置密钥库类型，并加载密钥库信息（密钥库密码设置）
        try {
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(keystoreStream, keystorePwd != null ? keystorePwd.toCharArray() : null);
            // 提取密钥信息
            Key key = keyStore.getKey(privateKeyAlias, privateKeyPwd != null ? privateKeyPwd.toCharArray() : null);
            if (key == null) {
                log.info("未在密钥库内获取到别名为：{} 的信息", privateKeyAlias);
            } else {
                resultPrivateKey = Base64.getEncoder().encodeToString(key.getEncoded());
                return resultPrivateKey;
            }
        } catch (KeyStoreException e) {
            log.info("密钥库操作失败: {}", e.getMessage(), e);
        } catch (IOException e) {
            log.info("读写文件时发生错误: {}", e.getMessage(), e);
        } catch (NoSuchAlgorithmException e) {
            log.info("请求的算法不存在: {}", e.getMessage(), e);
        } catch (CertificateException e) {
            log.info("证书操作或解析过程中发生了错误: {}", e.getMessage(), e);
        } catch (UnrecoverableKeyException e) {
            log.info("密钥库内私钥信息无法提取，可能密码错误，请检查密码: {}", e.getMessage(), e);
        }
        return resultPrivateKey;
    }

    /***
     * 根据传入的密钥库流信息解析密钥库内的全部证书
     * @param keystoreStream 密钥库流信息
     * @param keyStoreType 证书类型
     * @param password 密钥库密码（若密钥库未设置密码则直接传入null）
     * @return 返回全部证书信息
     */
    public static List<CertificateMessage> analysisKeystoreCertificates(
            InputStream keystoreStream, String keyStoreType, String password) {
        // 初始化信息
        List<CertificateMessage> certificateMessages = new ArrayList<>();

        try {
            // 构建密钥库对象，并设置密钥库类型，并加载密钥库信息（密钥库密码设置）
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(keystoreStream, password != null ? password.toCharArray() : null);
            // 获取密钥库全部别名条目信息
            Enumeration<String> aliases = keyStore.aliases();
            // 遍历信息
            while (aliases.hasMoreElements()) {
                // 获取别名信息
                String aliasName = aliases.nextElement();
                // 校验是否为证书信息，若是证书信息则进行解析，反之（公钥、密钥）不处理
                if (keyStore.entryInstanceOf(aliasName, KeyStore.TrustedCertificateEntry.class)) {
                    // 根据别名获取证书信息
                    Certificate certificate = keyStore.getCertificate(aliasName);
                    //解析并添加
                    certificateMessages.add(certificateParsing(certificate, aliasName));
                }
            }
        } catch (KeyStoreException e) {
            log.info("密钥库操作失败: {}", e.getMessage(), e);
        } catch (IOException e) {
            log.info("读写文件时发生错误: {}", e.getMessage(), e);
        } catch (NoSuchAlgorithmException e) {
            log.info("请求的算法不存在: {}", e.getMessage(), e);
        } catch (CertificateException e) {
            log.info("证书操作或解析过程中发生了错误: {}", e.getMessage(), e);
        }
        return certificateMessages;
    }

    /***
     * 证书解析（根据传入的证书流对象解析）
     * @param certificateStream 证书流对象
     * @return CertificateMessage证书封装类
     */
    public static CertificateMessage certificateParsing(InputStream certificateStream) {
        try {
            // 注：使用官方自带方式自己会解析如：”-----END  DIGITAL CERTIFICATE-----“
            //创建证书工厂对象
            CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
            // 使用证书工厂对象加载证书
            Certificate certificate = certificateFactory.generateCertificate(certificateStream);
            return certificateParsing(certificate, "");
        } catch (CertificateException e) {
            log.info("流文件处理异常：{}", e.getMessage());
            return new CertificateMessage();
        }
    }

    /***
     * 证书解析（根据传入的证书的Base64格式字符串解析）
     * @param certificateBase64 证书的Base64格式
     * @return CertificateMessage证书封装类
     */
    public static CertificateMessage certificateParsing(String certificateBase64) {
        try {
            //创建证书工厂对象
            CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
            // 将 Base64 字符串解码为证书字节数组
            byte[] certBytes = Base64.getDecoder().decode(certificateBase64);
            // 使用证书工厂对象加载证书
            ByteArrayInputStream input = new ByteArrayInputStream(certBytes);
            // 构建证书对象（其实为X509Certificate类型）
            Certificate certificate = certificateFactory.generateCertificate(input);
            return certificateParsing(certificate, "");
        } catch (CertificateException e) {
            log.info("证书解析异常：{}", e.getMessage());
            return new CertificateMessage();
        }
    }

    /***
     * 证书解析（根据传入的Certificate对象解析）
     * @param certificate 证书对象
     * @param alias 证书别名信息
     * @return CertificateMessage证书封装类
     */
    public static CertificateMessage certificateParsing(Certificate certificate, String alias) {
        return parseCertificate((X509Certificate) certificate, alias);
    }

    /***
     * 根据传入的公钥信息流对象进行解析，返回具体的Base64格式公钥；（这里以我生成的公钥来解析）
     * 注：具体解析规则可以自己修改
     * <h1>注：不推荐使用，具体的公钥可以通过解析证书获取<h1/>
     * @param publicKeyStream 公钥流信息对象
     * @return Base64格式的字符串公钥
     */
    @Deprecated
    public static String publicKeyParsing(InputStream publicKeyStream) {
        String publicKeyBase64Str = getStringInStream(publicKeyStream);
        // 剔除字符串无用的数据（具体如何剔除请自己指定，本着剔除文件无用的修饰等等）
        publicKeyBase64Str = publicKeyBase64Str.replace("-----BEGIN  PUBLIC KEY-----", "")
                .replace("-----END  PUBLIC KEY-----", "")
                .replace("\n", "")
                .replaceAll("\\s+", "");
        return publicKeyBase64Str;
    }

    /***
     * 根据传入的私钥信息流对象进行解析，返回具体的Base64格式私钥；（这里以我生成的私钥来解析）
     * 注：具体解析规则可以自己修改
     * <h1>注：不推荐使用，具体的私钥可以通过解析密钥库来获取，暴露在外的私钥文件不安全<h1/>
     * @param privateKeyStream 私钥流信息对象
     * @return Base64格式的字符串私钥
     */
    @Deprecated
    public static String privateKeyParsing(InputStream privateKeyStream) {
        String privateKeyBase64Str = getStringInStream(privateKeyStream);
        // 剔除字符串无用的数据（具体如何剔除请自己指定，本着剔除文件无用的修饰等等）
        privateKeyBase64Str = privateKeyBase64Str.replace("-----BEGIN  PRIVATE KEY-----", "")
                .replace("-----END  PRIVATE KEY-----", "")
                .replace("\n", "")
                .replaceAll("\\s+", "");
        return privateKeyBase64Str;
    }

    /***
     * 校验证书信息是否过期
     * @param certificateMessage 解析后并封装的证书信息
     * @return true代表证书过期活未生效，false代表证书未过期
     */
    public static boolean certificateVerificationTime(CertificateMessage certificateMessage) {
        // 初始化数据
        boolean overdue = false;
        // 获取证书解析原生对象
        X509Certificate certificate = (X509Certificate) certificateMessage.getCertificate();
        try {
            // 校验是否过期或未生效
            certificate.checkValidity();
        } catch (CertificateExpiredException e) {
            log.warn("当前操作的证书已过期：{}", e.getMessage());
            overdue = true;
        } catch (CertificateNotYetValidException e) {
            log.warn("当前操作的证书尚未生效：{}", e.getMessage());
            overdue = true;
        }
        return overdue;
    }

    /***
     * 根据传入的流对象，提取流中的字符串信息（只针对存储信息为字符串的文件流）
     * @param inputStream 输入流对象
     * @return 返回流中的字符串
     */
    private static String getStringInStream(InputStream inputStream) {
        // 初始化返回信息
        String resultStr = "";
        try {
            //流数据处理
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[1024];
            while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            resultStr = buffer.toString();
            // 关闭流
            buffer.close();
        } catch (IOException e) {
            log.info("文件流信息读取处理失败：{}", e.getMessage());
            throw new RuntimeException(e);
        }
        return resultStr;
    }

    /***
     * 根据证书信息进行数据解析
     * @param certificate 证书信息
     * @param alias 证书别名信息（若没有可以不写）
     * @return CertificateMessage证书封装类
     */
    private static CertificateMessage parseCertificate(X509Certificate certificate, String alias) {
        // 创建 CertificateMessage 对象，并设置证书别名、版本号、序列号等基本属性
        CertificateMessage message = new CertificateMessage();
        message.setCertificateAlias(alias);
        message.setVersion(certificate.getVersion());
        message.setSerialNumber(certificate.getSerialNumber());
        message.setCertificate(certificate);

        // 解析颁发者和主题信息
        X500Principal issuerPrincipal = certificate.getIssuerX500Principal();
        X500Principal subjectPrincipal = certificate.getSubjectX500Principal();
        CertificateMessage.IssuerMessage issuer = parseIssuerOrSubject(issuerPrincipal);
        CertificateMessage.IssuerMessage subject = parseIssuerOrSubject(subjectPrincipal);
        message.setIssuer(issuer);
        message.setSubject(subject);

        // 设置公钥、公钥算法、签名算法、证书有效期等信息
        message.setPublicKey(Base64.getEncoder().encodeToString(certificate.getPublicKey().getEncoded()));
        message.setPublicKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
        message.setSignatureAlgorithm(certificate.getSigAlgName());
        message.setNotBefore(certificate.getNotBefore());
        message.setNotAfter(certificate.getNotAfter());
        return message;
    }

    /***
     * 根据传入的证书基本数据（如国家、省份、城市、组织、部门、公司等）进行解析
     * @param principal 证书基本信息类
     * @return CertificateMessage.IssuerMessage证书基本信息对象
     */
    private static CertificateMessage.IssuerMessage parseIssuerOrSubject(X500Principal principal) {
        // 创建 CertificateMessage.IssuerMessage 对象
        CertificateMessage.IssuerMessage message = new CertificateMessage.IssuerMessage();
        // 如果传入的 X500Principal 对象为空，则直接返回
        if (principal == null) {
            return message;
        }
        // 获取 X500Principal 对象的名称字符串表示
        String name = principal.getName(X500Principal.RFC1779);
        // 将名称字符串按照逗号分隔成多个键值对，存储在 items 数组中
        String[] items = name.split(",");
        // 遍历 items 数组，解析每个键值对并设置到 message 对象中
        for (String item : items) {
            // 将键值对字符串按照等号分隔成两个部分
            String[] parts = item.trim().split("=");
            // 如果没有等号或者等号左右两边都为空，则跳过该键值对
            if (parts.length < 2) {
                continue;
            }
            // 获取键和值
            String key = parts[0];
            String value = parts[1];
            // 根据键的名称，设置对应的值到 message 对象中
            switch (key.toLowerCase()) {
                case "c": // 国家
                    message.setCountry(value);
                    break;
                case "st": // 省份
                    message.setProvince(value);
                    break;
                case "l": // 城市
                    message.setCity(value);
                    break;
                case "o": // 组织
                    message.setOrganization(value);
                    break;
                case "ou": // 部门
                    message.setDepartment(value);
                    break;
                case "cn": // 公司
                    message.setCompany(value);
                    break;
            }
        }
        // 返回 message 对象
        return message;
    }
}